// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).

using System;

using Tierless.Framework.Entities;

namespace Tierless.Framework.Data
{
	/// <summary>
	/// Provides a cache around the given entity accessor.
	/// </summary>
	public class EntityContext
	{
		/// <summary>
		/// Constructs a new entity context.
		/// </summary>
		/// <param name="entityAccessor">The entity accessor to use to retrieve entities, if necessary.</param>
		/// <param name="entities">The initial set of entities in the context.</param>
		public EntityContext(IEntityAccessor entityAccessor, EntitySets entities)
		{
			if (entityAccessor == null) throw new ArgumentNullException("AEntityAccessor");
			if (entities == null) throw new ArgumentNullException("AEntities");

			EntityAccessor = entityAccessor;
			Entities = entities;
		}

		/// <summary>
		/// Gets the entity access to use to retrieve entities.
		/// </summary>
		public IEntityAccessor EntityAccessor { get; private set; }

		/// <summary>
		/// Gets the set of entities in the context.
		/// </summary>
		public EntitySets Entities { get; private set; }

		/// <summary>
		/// Finds an entity by its ID.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to find.</param>
		/// <param name="entityId">The ID of the entity to find.</param>
		/// <returns>The entity, if it exists, null otherwise.</returns>
		public Entity FindEntity(string entityTypeName, EntityID entityId)
		{
			return FindEntity(entityTypeName, entityId, DateTime.Today);
		}

		/// <summary>
		/// Finds an entity by its ID.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to find.</param>
		/// <param name="entityId">The ID of the entity to find.</param>
		/// <param name="asOf">The effective date for the find.</param>
		/// <returns>The entity, if it exists, null otherwise.</returns>
		/// <remarks>
		/// If the entity is not found in the local entity context, an attempt is made to retrieve
		/// the entity from the entity accessor.
		/// </remarks>
		public Entity FindEntity(string entityTypeName, EntityID entityId, DateTime asOf)
		{
			Entity entity = null;
			EntitySet entitySet;
			var entityFound = false;
			if (Entities.TryGetValue(entityTypeName, out entitySet))
			{
				if (entitySet.ContainsKey(entityId))
				{
					entityFound = true;
					entity = entitySet.Get(entityId);
					if ((entity != null) && entity.IsTemporal)
						entityFound = entity.FromDate <= asOf;
				}
			}

			if (!entityFound || entity == null)
			{
				var requestedEntitySet = RequestEntity(new EntityGetByIDRequest(entityTypeName, entityId, asOf));
				if (entitySet == null)
				{
					Entities.Include(entityTypeName, requestedEntitySet);
					entitySet = requestedEntitySet;
					entity = entitySet.Get(entityId);
				}
				else if (entity == null && !entityFound)
				{
					entitySet.Merge(requestedEntitySet);
					entity = entitySet.Get(entityId);
				}
				else
				{
					entity = requestedEntitySet.Get(entityId);
				}
			}

			return entity;
		}

		/// <summary>
		/// Performs an entity request against the entity accessor.
		/// </summary>
		/// <param name="request">The entity request.</param>
		/// <returns>The requested entity set.</returns>
		/// <remarks>
		/// Note that this method is passthrough to the entity accessor, it does not affect the set of entities
		/// in the entity context, nor does it attempt to use the entity context to fulfill the request.
		/// </remarks>
		public EntitySet RequestEntity(EntityRequest request)
		{
			var requests = new EntityRequests { request };
			var results = EntityAccessor.RequestEntity(requests);
			return results[request.EntitySetName];
		}
	}
}